import { createAsyncThunk, createSlice, isRejected } from "@reduxjs/toolkit";
import { request } from "../util/Net";
import { RootState } from "./store";
import { isExceptionAction } from "./util";
import { encryptSha256 } from "../util/Crypto";
import { removeElementFromArray } from "../util/Arrays";
import { globalActions } from "./Global";

// 1. Data structure and combinations.

/**
 * Describe information of each user.
 */
interface UserEntry {
  /**
   * ID of the user, must be unique and auto-increment.
   */
  id: number;
  /**
   * User name, must be unique.
   */
  userName: string;
  /**
   * Authority of the user.
   */
  authority: string;
  /**
   * Register time of the user, in ISO format.
   */
  registerTime: string;
};

// 2. Requests and Responses, for communication with backend.

// 2.1 Requests.

/**
 * Request to fetch all users.
 * @path /service/user/fetchAll
 */
type FetchAllUsersRequest = null;

/**
 * Request to create a new user.
 * @path /service/user/create
 */
interface CreateUserRequest {
  /**
   * User name, must be unique.
   */
  userName: string;
  /**
   * Password encrypted in SHA-256, 32 characters.
   */
  password: string;
  /**
   * Authority of the user.
   */
  authority: string;
};

/**
 * Request to update user information.
 * @path /service/user/update
 */
interface UpdateUserRequest {
  /**
   * ID of the user to update information.
   */
  id: number;
  /**
   * User name, must be unique.
   */
  userName: string;
  /**
   * Authority of the user.
   */
  authority: string;
};

/**
 * Request to delete user.
 * @path /service/user/delete
 */
interface DeleteUserRequest {
  /**
   * ID of the user to delete.
   */
  id: number;
};

// 2.2 Responses.

/**
 * Response from fetch all users module.
 * @errcode 10004
 * @path /service/user/fetchAll
 */
type FetchAllUsersResponse = UserEntry[];

/**
 * Response from create user module.
 * @errcode 10005
 * @path /service/user/create
 */
interface CreateUserResponse {
  /**
   * Auto-generated user id, must be unique and auto-increment.
   */
  id: number;
  /**
   * Register time of the user, in ISO format.
   */
  registerTime: string;
};

/**
 * Response from update user information.
 * @errcode 10005
 * @path /service/user/update
 */
type UpdateUserResponse = null;

/**
 * Response from delete specified user.
 * @errcode 10006
 * @path /service/user/delete
 */
type DeleteUserResponse = null;

// 3. Params for reducers.

/**
 * Params for creating new user.
 */
interface CreateUserParams {
  /**
   * User name, must be unique.
   */
  userName: string;
  /**
   * Plain text password.
   */
  password: string;
  /**
   * Authority of the user.
   */
  authority: string;
};

/**
 * Params for updating user.
 */
interface UpdateUserParams {
  /**
   * ID of the user to update information.
   */
  id: number;
  /**
   * User name, must be unique.
   */
  userName: string;
  /**
   * Authority of the user.
   */
  authority: string;
};

/**
 * Params for deleting user.
 */
interface DeleteUserParams {
  /**
   * ID of the user to delete.
   */
  id: number;
};

// 4. State and initial value definition.

/**
 * Redux state repository for this slice.
 */
interface UserManagementState {
  loaded: boolean;
  entries: UserEntry[];
};

/**
 * Initial state of this slice.
 */
const initialState: UserManagementState = {
  loaded: false,
  entries: []
};

// 5. Async functions definition.

/**
 * Async reducer.
 * Fetch all users and update the states.
 * @example const dispatch = useAppDispatch();
 * dispatch(fetchAllUsers());
 */
const fetchAllUsers = createAsyncThunk('api/fetchAllUsers', async (_: void, thunkAPI) => {
  const response = await request<FetchAllUsersResponse, FetchAllUsersRequest>('/service/user/fetchAll');
  if (response.data.code !== 0) {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Error', message: response.data.message, type: 'warning' }));
    return thunkAPI.rejectWithValue(response.data);
  }
  return response.data;
});

/**
 * Async reducer.
 * Create a new user with given information.
 * @param params params for create new user, including user name, plain-text password and authority
 * @example const dispatch = useAppDispatch();
 * dispatch(createNewUser({ userName: 'user', password: 'plain_password', authority: 'admin' }));
 */
const createNewUser = createAsyncThunk('api/createNewUser', async (params: CreateUserParams, thunkAPI) => {
  // Encrypt the plain text password.
  const passwordHash: string = encryptSha256(params.password);

  // Send request, without token.
  const response = await request<CreateUserResponse, CreateUserRequest>('/service/user/create', {
    userName: params.userName,
    password: passwordHash,
    authority: params.authority
  });

  if (response.data.code !== 0) {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Error', message: response.data.message, type: 'warning' }));
    return thunkAPI.rejectWithValue(response.data);
  }
  else {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Success', message: 'User successfully created.', type: 'success' }));
  }

  // Return the json response of the net communication.
  return response.data;
});

/**
 * Async reducer.
 * Update the specified user by ID.
 * @param params params for updating user information, including id, user name and authority
 * @example const dispatch = useAppDispatch();
 * dispatch(updateUser({ id: 9, userName: 'user', authority: 'admin' }));
 */
const updateUser = createAsyncThunk('api/updateUser', async (params: UpdateUserParams, thunkAPI) => {
  const response = await request<UpdateUserResponse, UpdateUserRequest>('/service/user/update', {
    id: params.id,
    userName: params.userName,
    authority: params.authority
  });

  if (response.data.code !== 0) {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Error', message: response.data.message, type: 'warning' }));
    return thunkAPI.rejectWithValue(response.data);
  }
  else {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Success', message: 'User successfully updated.', type: 'success' }));
  }

  return response.data;
});

/**
 * Async reducer.
 * Delete the specified user by ID.
 * @param params params for deleting user, specifing id
 * @example const dispatch = useAppDispatch();
 * dispatch(deleteUser({ id: 9 }));
 */
const deleteUser = createAsyncThunk('api/deleteUser', async (params: DeleteUserParams, thunkAPI) => {
  const response = await request<DeleteUserResponse, DeleteUserRequest>('/service/user/delete', {
    id: params.id
  });

  if (response.data.code !== 0) {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Error', message: response.data.message, type: 'warning' }));
    return thunkAPI.rejectWithValue(response.data);
  }
  else {
    thunkAPI.dispatch(globalActions.postAlert({ title: 'Success', message: 'User successfully deleted.', type: 'success' }));
  }

  return response.data;
});

// 6. Slice definition.

/**
 * Slice of the module Analyze.
 */
const userManagementSlice = createSlice({
  name: 'userManagement',
  initialState: initialState,
  reducers: {

  },
  extraReducers: (builder) => {
    builder.addCase(fetchAllUsers.fulfilled, (state, action) => {
      state.loaded = true;
      state.entries = action.payload.result;
    }).addCase(createNewUser.fulfilled, (state, action) => {
      state.entries.push({
        id: action.payload.result.id,
        userName: action.meta.arg.userName,
        authority: action.meta.arg.authority,
        registerTime: action.payload.result.registerTime
      });
      state.entries.sort((a, b) => a.id - b.id);
    }).addCase(updateUser.fulfilled, (state, action) => {
      const entry = state.entries.find(item => item.id === action.meta.arg.id);
      if (entry !== undefined) {
        entry.userName = action.meta.arg.userName;
        entry.authority = action.meta.arg.authority;
      }
    }).addCase(deleteUser.fulfilled, (state, action) => {
      if (action.payload.code === 0) {
        const index: number = state.entries.findIndex(item => item.id === action.meta.arg.id);
        if (index !== -1) {
          state.entries = removeElementFromArray(state.entries, index);
        }
      }
    }).addMatcher(isRejected, (state, action) => {

    }).addMatcher(isExceptionAction, (state, action) => {

    });
  }
});

// 7. Selectors for state in this module.

export const selectUserEntriesLoaded = (state: RootState) => state.UserManagement.loaded;
export const selectUserEntries = (state: RootState) => state.UserManagement.entries;

// 8. Export necessary modules.

export const userManagementActions = userManagementSlice.actions;
export type { UserEntry };
export { fetchAllUsers, createNewUser, updateUser, deleteUser };
export default userManagementSlice.reducer;
